// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.adwords.lib.client;

import com.google.api.ads.adwords.lib.conf.AdWordsApiConfiguration;
import com.google.api.ads.adwords.lib.conf.AdWordsBuildConfiguration;
import com.google.api.ads.common.lib.auth.OAuth2AuthorizationHeaderProvider;
import com.google.api.ads.common.lib.auth.OAuthAuthorizationHeaderProvider;
import com.google.api.ads.common.lib.client.HeaderHandler;
import com.google.api.ads.common.lib.exception.AuthenticationException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ServiceException;
import com.google.api.ads.common.lib.soap.SoapClientHandlerInterface;
import com.google.api.client.http.GenericUrl;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.inject.Inject;

import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;

/**
 * AdWords implementation of {@link HeaderHandler} for JAX-WS.
 *
 * @author Joseph DiLallo
 */
public class AdWordsJaxWsHeaderHandler implements
    HeaderHandler<AdWordsSession, AdWordsServiceDescriptor> {

  private static final String REQUEST_HEADER_LOCAL_PART = "RequestHeader";
  private final SoapClientHandlerInterface<Object> soapClientHandler;
  private final OAuthAuthorizationHeaderProvider oAuthAuthorizationHeaderProvider;
  private final OAuth2AuthorizationHeaderProvider oAuth2AuthorizationHeaderProvider;
  private final AdWordsApiConfiguration adWordsApiConfiguration;
  private final AdWordsBuildConfiguration adWordsBuildConfiguration;

  /**
   * Constructor.
   *
   * @param soapClientHandler the SOAP client handler
   * @param oAuthAuthorizationHeaderProvider the OAuth header provider
   * @param oAuth2AuthorizationHeaderProvider the OAuth2 header provider
   * @param adWordsApiConfiguration the AdWords API configuration
   * @param adWordsBuildConfiguration the AdWords build configuration
   */
  @SuppressWarnings("unchecked") // All generics of SoapClientHandlerInterface
                                 // extend Object.
  @Inject
  public AdWordsJaxWsHeaderHandler(
      @SuppressWarnings("rawtypes") /* Due to problem with guice binding */
      SoapClientHandlerInterface soapClientHandler,
      OAuthAuthorizationHeaderProvider oAuthAuthorizationHeaderProvider,
      OAuth2AuthorizationHeaderProvider oAuth2AuthorizationHeaderProvider,
      AdWordsApiConfiguration adWordsApiConfiguration,
      AdWordsBuildConfiguration adWordsBuildConfiguration) {
    this.oAuthAuthorizationHeaderProvider = oAuthAuthorizationHeaderProvider;
    this.oAuth2AuthorizationHeaderProvider = oAuth2AuthorizationHeaderProvider;
    this.soapClientHandler = soapClientHandler;
    this.adWordsApiConfiguration = adWordsApiConfiguration;
    this.adWordsBuildConfiguration = adWordsBuildConfiguration;
  }

  /**
   * @see HeaderHandler#setHeaders(Object,
   *      com.google.api.ads.common.lib.client.AdsSession,
   *      com.google.api.ads.common.lib.client.AdsServiceDescriptor)
   */
  public void setHeaders(Object soapClient, AdWordsSession adWordsSession,
      AdWordsServiceDescriptor adWordsServiceDescriptor) throws AuthenticationException,
      ServiceException {
    Map<String, Object> headerData = readHeaderElements(adWordsSession);
    setAuthenticationHeaders(soapClient, headerData, adWordsSession);
    soapClientHandler.setHeader(soapClient, null, null,
        constructSoapHeader(headerData, adWordsServiceDescriptor));
  }

  /**
   * Sets the authentication headers.
   *
   * @param soapClient the SOAP client
   * @param headerElements the map housing header elements
   * @param adWordsSession the AdWords session
   * @throws OAuthException if there was a problem getting/setting the OAuth
   *     header
   */
  @VisibleForTesting
  void setAuthenticationHeaders(Object soapClient, Map<String, Object> headerElements,
      AdWordsSession adWordsSession) throws OAuthException {
    if (adWordsSession.getClientLoginToken() != null) {
      headerElements.put("authToken", adWordsSession.getClientLoginToken());
    } else if (adWordsSession.getOAuthParameters() != null) {
      final String authorizationHeader =
          oAuthAuthorizationHeaderProvider.getOAuthAuthorizationHeader(
              adWordsSession, new GenericUrl(soapClientHandler.getEndpointAddress(soapClient)));
      soapClientHandler.setHttpHeaders(soapClient, new HashMap<String, String> () {{
        put("Authorization", authorizationHeader);
      }});
    } else if (adWordsSession.getOAuth2Credential() != null) {
      final String authorizationHeader =
          oAuth2AuthorizationHeaderProvider.getOAuth2AuthorizationHeader(adWordsSession);
      soapClientHandler.setHttpHeaders(soapClient, new HashMap<String, String> () {{
        put("Authorization", authorizationHeader);
      }});
    }
  }

  /**
   * Creates a map with data from the user's service session which is needed to
   * set AdWords SOAP headers.
   *
   * @param adWordsSession the user's session object
   * @return a map of HTTP header names to values
   */
  private Map<String, Object> readHeaderElements(AdWordsSession adWordsSession) {
    Map<String, Object> mapToFill = Maps.newHashMap();
    mapToFill.put("developerToken", adWordsSession.getDeveloperToken());
    mapToFill.put("clientCustomerId", adWordsSession.getClientCustomerId());
    mapToFill.put("userAgent", generateLibSignature() + "|" + adWordsSession.getUserAgent());
    mapToFill.put("validateOnly", adWordsSession.isValidateOnly());
    mapToFill.put("partialFailure", adWordsSession.isPartialFailure());
    return mapToFill;
  }

  /**
   * Generates a library signature from the configuration files.
   */
  String generateLibSignature() {
    return adWordsBuildConfiguration.getLibPrefix() + "-"
        + adWordsBuildConfiguration.getLibPostfix() + "-"
        + adWordsBuildConfiguration.getLibVersion();
  }

  /**
   * Constructs a SOAP header object ready to be attached to a JAX-WS client.
   *
   * @param headerData a map of SOAP header element names to values
   * @param adWordsServiceDescriptor the descriptor of which service's headers
   *     are being created
   * @return a SOAP header object
   */
  private SOAPElement constructSoapHeader(Map<String, Object> headerData,
      AdWordsServiceDescriptor adWordsServiceDescriptor) {
    String namespace =
        adWordsApiConfiguration.getNamespacePrefix() + "/"
            + adWordsServiceDescriptor.getPackageGroup() + "/"
            + adWordsServiceDescriptor.getVersion();
    String requestHeaderNamespace =
        adWordsApiConfiguration.getNamespacePrefix() + "/cm/"
            + adWordsServiceDescriptor.getVersion();

    try {
      SOAPFactory soapFactory = SOAPFactory.newInstance();
      SOAPElement requestHeader = soapFactory.createElement(new QName(namespace,
          REQUEST_HEADER_LOCAL_PART));
      for (String headerElementName : headerData.keySet()) {
        if (headerData.get(headerElementName) != null) {
          SOAPElement newElement = requestHeader.addChildElement(headerElementName, null,
              requestHeaderNamespace);
          newElement.addTextNode(headerData.get(headerElementName).toString());
        }
      }
      return requestHeader;
    } catch (SOAPException e) {
      throw new ServiceException("Unexpected exception.", e);
    }
  }
}
